package com.hup.utils.swing.screenShot;

import com.hup.utils.algorithm.image.MagicWand;
import com.hup.utils.algorithm.image.MagicWand.ThresholdHandler;
import com.hup.utils.commons.NumberUtil;
import com.hup.utils.commons.hardware.MouseController;
import com.hup.utils.commons.hardware.ScreenUtil;
import com.hup.utils.commons.hardware.SystemUtil;
import com.hup.utils.commons.start.DialogHelper;
import com.hup.utils.commons.start.LookThemeUtil;
import com.hup.utils.swing.ColorUtil;
import com.hup.utils.swing.DimensionUtil;
import com.hup.utils.swing.GraphicsUtil;
import com.hup.utils.swing.ImageUtil;
import com.hup.utils.swing.screenShot.ScreenShotUtil.ShotHandler;
import com.hup.utils.swing.screenShot.ScreenShotUtil.ShotType;
import lombok.extern.slf4j.Slf4j;

import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.KeyStroke;
import java.awt.AWTException;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Stroke;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.image.BufferedImage;

/**
 * <pre>
 * 模式:取色,截图
 * 功能:
 * .绘制浮动放大镜窗口
 *      .放大镜
 *      .颜色信息
 *      .按键提示信息
 * .按键功能
 *      .F5:刷新屏幕截图
 *      .Z,X:复制颜色
 *      .双击,回车:取色/截图
 *      .ESC:
 *      .方向键(可加ctrl)
 *
 * 取色模式:
 *      .ESC:退出窗口
 *      .方向键等:移动鼠标
 *
 * 截图模式:
 * .绘制选框
 *      .鼠标选框
 *      .非锁定时
 *          .随鼠标,魔棒选框
 *          .方向键等:移动鼠标
 *          .点击,拖拽后:锁定
 *          .ESC:退出窗口
 *      .锁定时
 *          .方向键等:没按下鼠标时移动选框,按下鼠标时移动鼠标
 *          .ESC,右击:解除锁定
 * </pre>
 *
 * @author hugan
 * @date 2020/5/8
 */
@Slf4j
class ScreenShotLabel extends JLabel {

    protected final LabelCallback callback;
    protected MagnifierWindow magnifierWindow;
    protected SelectionWindow selectionWindow;
    private MouseController mouseController;

    private BufferedImage screenShot;
    /**
     * F5刷新时,不会重置;窗口退出后才重置
     */
    protected int rgbThreshold = ThresholdHandler.DEFAULT_RGB_THRESHOLD;
    /**
     * 魔棒算法,匹配方式:4邻域
     */
    protected MagicWand.Helper magicWandHelper = MagicWand.Helper.create().isEight(false).thresholdHandler(new ThresholdHandler() {
        @Override
        public int getRgbThreshold() {
            return rgbThreshold;
        }
    });

    /**
     * 鼠标可以到达的坐标
     */
    protected Insets screenCoor;

    /**
     * 鼠标在窗口内的坐标
     */
    protected int curX, curY;
    /**
     * 截图选区锁定状态
     */
    protected boolean lockSelection;
    /**
     * 截图被选择的区域
     */
    protected Rectangle selectRect;

    public ScreenShotLabel(LabelCallback callback) {
        this.callback = callback;
        setFont(new Font("宋体", Font.PLAIN, (int) (14 * LookThemeUtil.getScreenScale())));//小一号的字体
        magnifierWindow = new MagnifierWindow();
        selectionWindow = new SelectionWindow();
        initListener();
    }

    private void initListener() {
        MyMouseFunction mouseAdapter = new MyMouseFunction();
        addMouseListener(mouseAdapter);
        addMouseMotionListener(mouseAdapter);
        addMouseWheelListener(mouseAdapter);

        registerKeyboardAction(e -> callback.getFrame().dispose(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
                JComponent.WHEN_IN_FOCUSED_WINDOW);//不用窗口某个控件有焦点也可以激活按键
        registerKeyboardAction(e -> applyResult(), KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0),
                JComponent.WHEN_IN_FOCUSED_WINDOW);
        registerKeyboardAction(e -> refreshImage(), KeyStroke.getKeyStroke(KeyEvent.VK_F5, 0),
                JComponent.WHEN_IN_FOCUSED_WINDOW);
        registerKeyboardAction(e -> copyColorAndExit(true), KeyStroke.getKeyStroke(KeyEvent.VK_Z, 0),
                JComponent.WHEN_IN_FOCUSED_WINDOW);
        registerKeyboardAction(e -> copyColorAndExit(false), KeyStroke.getKeyStroke(KeyEvent.VK_X, 0),
                JComponent.WHEN_IN_FOCUSED_WINDOW);

        try {
            mouseController = new MouseController(null);
        } catch (AWTException e) {
            DialogHelper.show("鼠标控制器初始化失败,", e, callback.getFrame());
        }
        MyKeyFunction keyFunction = new MyKeyFunction();
        //方向键
        registerKeyboardAction(e -> keyFunction.keyUp(false),
                KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
        registerKeyboardAction(e -> keyFunction.keyDown(false),
                KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
        registerKeyboardAction(e -> keyFunction.keyLeft(false),
                KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
        registerKeyboardAction(e -> keyFunction.keyRight(false),
                KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
        //ctrl+key
        registerKeyboardAction(e -> keyFunction.keyUp(true),
                KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.CTRL_DOWN_MASK), JComponent.WHEN_IN_FOCUSED_WINDOW);
        registerKeyboardAction(e -> keyFunction.keyDown(true),
                KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.CTRL_DOWN_MASK), JComponent.WHEN_IN_FOCUSED_WINDOW);
        registerKeyboardAction(e -> keyFunction.keyLeft(true),
                KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, InputEvent.CTRL_DOWN_MASK), JComponent.WHEN_IN_FOCUSED_WINDOW);
        registerKeyboardAction(e -> keyFunction.keyRight(true),
                KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, InputEvent.CTRL_DOWN_MASK), JComponent.WHEN_IN_FOCUSED_WINDOW);
        //ctrl+A
        registerKeyboardAction(e -> {
                    lockSelection = true;
                    selectRect.x = selectRect.y = 0;
                    selectRect.width = screenCoor.right - screenCoor.left + 1;
                    selectRect.height = screenCoor.bottom - screenCoor.top + 1;
                    repaint();
                },
                KeyStroke.getKeyStroke(KeyEvent.VK_A, InputEvent.CTRL_DOWN_MASK), JComponent.WHEN_IN_FOCUSED_WINDOW);
    }

    @Override
    public void paint(Graphics g1) {
        Graphics2D g = GraphicsUtil.antiAlias((Graphics2D) g1);
        //绘制截屏图像
        int width = getWidth();
        int height = getHeight();
        g.drawImage(screenShot, 0, 0, width, height, null);

        if (selectRect != null) {
            selectionWindow.paint(g);
        }

        if (curX != -1) {
            magnifierWindow.paint(g);
        }
    }

    @SuppressWarnings("unchecked")
    protected void applyResult() {
        //窗口展示后,但鼠标未进入窗口(调用者窗口置顶,鼠标在另一个屏幕):屏蔽键盘事件
        if (curX == -1) return;

        //log.info("");
        switch (callback.shotType()) {
            case COLOR:
                int argb = screenShot.getRGB(curX, curY);
                callback.callerHandler().onSelected(new Color(argb, true), curX, curY, 1, 1);
                break;
            case IMAGE:
                BufferedImage res = ImageUtil.subSafely(screenShot, selectRect);
                callback.callerHandler().onSelected(res, selectRect.x, selectRect.y, selectRect.width, selectRect.height);
                break;
        }
        callback.getFrame().dispose();
    }

    private void copyColorAndExit(boolean isInt) {
        if (curX == -1) return;
        int argb = screenShot.getRGB(curX, curY);
        SystemUtil.saveToClipboard(isInt ? ColorUtil.getIntText(argb) : ColorUtil.getHexText(argb));
        callback.getFrame().dispose();
    }

    /**
     * 刷新屏幕图像
     */
    public void refreshImage() {
        log.debug("");
        JFrame parent = callback.getFrame();
        boolean parentVisible = parent.isVisible();
        if (parentVisible) parent.setVisible(false);

        //刷新屏幕图像
        if (screenShot != null) screenShot.flush();
        screenShot = ScreenShotUtil.getAllScreenImage(callback.includeSidebar());
        magicWandHelper.init(screenShot);
        if (callback.includeSidebar()) {
            screenCoor = ScreenUtil.getAllScreenInsets();
        } else {
            screenCoor = ScreenUtil.getScreenUsableCoor();
        }

        if (parentVisible) parent.setVisible(true);
    }

    public void flushAndReset() {
        //log.info("");
        if (screenShot != null) {
            screenShot.flush();
            screenShot = null;
            magicWandHelper.flush();
        }
        rgbThreshold = ThresholdHandler.DEFAULT_RGB_THRESHOLD;
        //重置鼠标位置:避免frame首次展示时有位置不对的放大镜
        curX = curY = -1;
        lockSelection = false;
        selectRect = null;
    }

    /**
     * 移动选区时:计算新的选区坐标后,处理边缘越界问题
     */
    protected void fixMovedRect() {
        int xMin, xMax, yMin, yMax;
        if (callback.includeSidebar()) {
            xMin = yMin = 0;
            xMax = screenCoor.right - screenCoor.left;
            yMax = screenCoor.bottom - screenCoor.top;
        } else {
            Insets usableCoor = ScreenUtil.getScreenUsableCoor();
            xMin = usableCoor.left;
            xMax = usableCoor.right;
            yMin = usableCoor.top;
            yMax = usableCoor.bottom;
        }
        if (selectRect.x < xMin) {
            selectRect.x = xMin;
        } else if (selectRect.x + selectRect.width - 1 > xMax) {
            selectRect.x = xMax - selectRect.width + 1;
        }
        if (selectRect.y < yMin) {
            selectRect.y = yMin;
        } else if (selectRect.y + selectRect.height - 1 > yMax) {
            selectRect.y = yMax - selectRect.height + 1;
        }
    }

    private class MyKeyFunction {

        public void keyUp(boolean ctrlDown) {
            int px = !ctrlDown ? 1 : 10;
            if (callback.shotType() == ShotType.COLOR || !lockSelection) {
                mouseController.moveDiff(0, -px);
            } else {
                selectRect.y -= px;
                if (selectRect.y < 0) selectRect.y = 0;
                repaint();
            }
        }

        public void keyDown(boolean ctrlDown) {
            int px = !ctrlDown ? 1 : 10;
            if (callback.shotType() == ShotType.COLOR || !lockSelection) {
                mouseController.moveDiff(0, px);
            } else {
                selectRect.y += px;
                fixMovedRect();
                repaint();
            }
        }

        public void keyLeft(boolean ctrlDown) {
            int px = !ctrlDown ? 1 : 10;
            if (callback.shotType() == ShotType.COLOR || !lockSelection) {
                mouseController.moveDiff(-px, 0);
            } else {
                selectRect.x -= px;
                if (selectRect.x < 0) selectRect.x = 0;
                repaint();
            }
        }

        public void keyRight(boolean ctrlDown) {
            int px = !ctrlDown ? 1 : 10;
            if (callback.shotType() == ShotType.COLOR || !lockSelection) {
                mouseController.moveDiff(px, 0);
            } else {
                selectRect.x += px;
                fixMovedRect();
                repaint();
            }
        }
    }

    private class MyMouseFunction extends MouseAdapter {

        ColorMouseGesture colorMouseGesture = new ColorMouseGesture(ScreenShotLabel.this);
        ImageMouseGesture imageMouseGesture = new ImageMouseGesture(ScreenShotLabel.this);

        @Override
        public void mouseEntered(MouseEvent e) {
            if (callback.shotType() == ShotType.COLOR) {
                colorMouseGesture.mouseEntered(e);
            } else {
                imageMouseGesture.mouseEntered(e);
            }
        }

        @Override
        public void mouseMoved(MouseEvent e) {
            if (callback.shotType() == ShotType.COLOR) {
                colorMouseGesture.mouseMoved(e);
            } else {
                imageMouseGesture.mouseMoved(e);
            }
        }

        @Override
        public void mousePressed(MouseEvent e) {
            if (callback.shotType() == ShotType.IMAGE) {
                imageMouseGesture.mousePressed(e);
            }
        }

        @Override
        public void mouseDragged(MouseEvent e) {
            int x = e.getX();
            int y = e.getY();
            if (callback.shotType() == ShotType.COLOR) {
                colorMouseGesture.mouseDragged(e);
            } else {
                imageMouseGesture.mouseDragged(e);
            }
        }

        @Override
        public void mouseReleased(MouseEvent e) {
            if (callback.shotType() == ShotType.IMAGE) {
                imageMouseGesture.mouseReleased(e);
            }
        }

        @Override
        public void mouseClicked(MouseEvent e) {
            ShotType shotType = callback.shotType();
            if (callback.shotType() == ShotType.COLOR) {
                colorMouseGesture.mouseClicked(e);
            } else {
                imageMouseGesture.mouseClicked(e);
            }
        }

        @Override
        public void mouseWheelMoved(MouseWheelEvent e) {
            if (callback.shotType() == ShotType.IMAGE) {
                imageMouseGesture.mouseWheelMoved(e);
            }
        }
    }

    /**
     * 截屏模式的选区图像
     */
    protected class SelectionWindow {
        /**
         * 被选择的区域,线
         */
        private final Stroke MAGIC_WAND_DASH = new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 3.5f, new float[]{13, 6}, 0f);
        private final Color textBg = new Color(0, 0, 0, 100);
        private final int charWidth, charHeight, descent;

        private SelectionWindow() {
            FontMetrics metrics = getFontMetrics(getFont());
            charWidth = metrics.charWidth('x');
            charHeight = metrics.getHeight();
            descent = metrics.getDescent();
        }

        private void paint(Graphics2D g) {
            //当前选框
            g.setColor(Color.RED);
            g.setStroke(MAGIC_WAND_DASH);
            g.drawRect(selectRect.x, selectRect.y, selectRect.width - 1, selectRect.height - 1);
            /*
            选区文字:"x,y width X height"
            .一般:在选框外左上方
                .选框y<文字高度:在选框内左上方
                    .选框<文字尺寸:在选框外左下方
             */
            Rectangle bg;
            //背景宽度=[空格*3,x,]5+[x,y,width,height]字符个数
            int bgWidth = charWidth * (5 + NumberUtil.getDigitCount(selectRect.x) + NumberUtil.getDigitCount(selectRect.y)
                    + NumberUtil.getDigitCount(selectRect.width) + NumberUtil.getDigitCount(selectRect.height));
            if (selectRect.y > charHeight + 1) {
                //选框外左上方
                bg = new Rectangle(selectRect.x + 1, selectRect.y - charHeight + 1, bgWidth, charHeight);
            } else if (selectRect.width - 2 > bgWidth * 3 && selectRect.height - 2 > charHeight * 3) {
                //选框内左上方
                bg = new Rectangle(selectRect.x + 1, selectRect.y + 1, bgWidth, charHeight);
            } else {
                //选框外左下方
                bg = new Rectangle(selectRect.x + 1, selectRect.y + selectRect.height, bgWidth, charHeight);
            }
            g.setColor(textBg);
            g.fillRect(bg.x, bg.y, bg.width - 1, bg.height - 1);
            g.setColor(Color.WHITE);
            g.drawString(selectRect.x + "," + selectRect.y + " " + selectRect.width + "x" + selectRect.height, bg.x + charWidth, bg.y + charHeight - 1 - descent);
        }
    }

    /**
     * 放大镜窗口(纯图像对象),包括:放大镜图像,文字信息
     */
    protected class MagnifierWindow {
        private final BasicStroke DEFAULT_STROKE = new BasicStroke(1);
        private final BasicStroke MAGNIFIER_STROKE = new BasicStroke(11, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL);
        private final Color MAGNIFIER_STROKE_COLOR = new Color(178, 211, 250, 80);

        /**
         * 文字高度,初始化时固定成基数
         */
        int charHeight;
        int infoPadding = 10;
        /**
         * 浮动框大小,放大镜区域高度,初始化时固定成基数
         */
        int winWidth, winHeight, magnifierHeight;
        /**
         * 窗口图像
         */
        int winX, winY;

        private MagnifierWindow() {
            //测量文本尺寸
            Dimension sizeByText = DimensionUtil.getSizeByText(ScreenShotLabel.this, "255,255,255 F5刷新 ");
            charHeight = (int) sizeByText.getHeight();
            //保证宽度是基数:左n+1+右n个像素
            if (charHeight % 2 == 0) charHeight += 1;
            winWidth = (int) (sizeByText.getWidth() + charHeight + infoPadding * 2);//charHeight是当前颜色小框的宽度
            if (winWidth % 2 == 0) winWidth += 1;
            //放大镜:文本高=3/2
            int infoTextHeight = charHeight * 4;//底部文本高度,3行
            winHeight = (int) (infoTextHeight / 0.4);
            magnifierHeight = (int) (winHeight * 0.6);
            if (magnifierHeight % 2 == 0) magnifierHeight += 1;
            log.debug("winWidth={}, winHeight={}, magnifierHeight={}", winWidth, winHeight, magnifierHeight);
        }

        /**
         * 根据鼠标位置和当前label尺寸,更新窗口的位置
         */
        protected void updateLocation(int curX, int curY) {
            int padding = 28;//窗口距离鼠标的间隔
            winX = curX + padding;
            winY = curY + padding;
            if (winX + winWidth > getWidth()) winX = curX - padding - winWidth;
            if (winY + winHeight > getHeight()) winY = curY - padding - winHeight;
        }

        @SuppressWarnings("SuspiciousNameCombination")
        private void paint(Graphics2D g) {
            //边框
            g.setStroke(DEFAULT_STROKE);
            g.setColor(Color.GRAY);
            if (callback.shotType() == ShotType.COLOR) {
                g.fillRect(winX + 1, winY + magnifierHeight, winWidth - 2, winHeight - magnifierHeight);
                g.drawRect(winX, winY, winWidth - 1, winHeight - 1);
            } else {
                g.fillRect(winX + 1, winY + magnifierHeight, winWidth - 2, winHeight - magnifierHeight + charHeight);
                g.drawRect(winX, winY, winWidth - 1, winHeight - 1 + charHeight);
            }

            //颜色框,坐标文本
            int argb = screenShot.getRGB(curX, curY);
            int x = winX + infoPadding;
            int y = winY + magnifierHeight + infoPadding;
            g.setColor(Color.WHITE);
            g.drawRect(x, y, charHeight, charHeight);
            g.drawString("(" + curX + "," + curY + ") F5刷新", x + charHeight + infoPadding, y + charHeight);
            g.setColor(new Color(argb));
            g.fillRect(x + 1, y + 1, charHeight - 1, charHeight - 1);

            //颜色文本
            g.setColor(Color.WHITE);
            String intText = ColorUtil.getIntText(argb) + " Z复制";
            String hexText = "#" + ColorUtil.getHexText(argb) + " X复制";
            g.drawString(intText, x, y + charHeight * 2);
            g.drawString(hexText, x, y + charHeight * 3);
            if (callback.shotType() == ShotType.IMAGE) {
                g.drawString("容差:" + rgbThreshold + (!lockSelection ? " 滚轮调节" : ""), x, y + charHeight * 4);
            }

            //放大镜:截图高度是一个文字的高度,按放大镜尺寸保持截取比例
            int h = charHeight;
            int w = (int) (winWidth / (magnifierHeight + 0d) * h);
            if (w % 2 == 0) w++;
            x = (int) (curX - (w - 1) / 2.0);
            y = (int) (curY - (h - 1) / 2.0);

            //放大图像,马赛克背景
            Rectangle rectMagnifier = new Rectangle(winX + 1, winY + 1, winWidth - 2, magnifierHeight);
            ImageUtil.fillMosaic(g, rectMagnifier);
            BufferedImage sub = ImageUtil.subSafely(screenShot, x, y, w, h);
            g.drawImage(sub, rectMagnifier.x, rectMagnifier.y, rectMagnifier.width, rectMagnifier.height, null);
            sub.flush();

            //放大镜十字线:中间点是空的
            g.setColor(MAGNIFIER_STROKE_COLOR);
            g.setStroke(MAGNIFIER_STROKE);
            int rectLength = (int) (MAGNIFIER_STROKE.getLineWidth() + 4);//中心矩形的边长
            int hLineWid = (winWidth - 2 - rectLength) / 2;//每条横线的水平宽度
            //
            x = winX + 1;
            y = winY + 1 + (magnifierHeight - 1) / 2;
            g.drawLine(x, y, x + hLineWid - 1, y);//横-左
            x = winX + 1 + hLineWid + rectLength;
            g.drawLine(x, y, x + hLineWid - 1, y);//横-右
            //
            int vLineHeight = (magnifierHeight - rectLength) / 2;
            x = winX + (winWidth - 1) / 2;
            y = winY + 1;
            g.drawLine(x, y, x, y + vLineHeight - 1);//竖-上
            y = winY + 1 + vLineHeight + rectLength;
            g.drawLine(x, y, x, y + vLineHeight - 1);//竖-下
            //中心边框
            g.setColor(Color.BLACK);
            g.setStroke(DEFAULT_STROKE);
            x = winX + 1 + hLineWid;
            y = winY + 1 + vLineHeight;
            g.drawRect(x, y, rectLength - 1, rectLength - 1);
        }
    }

    public interface LabelCallback {
        JFrame getFrame();

        boolean includeSidebar();

        ShotType shotType();

        ShotHandler callerHandler();
    }

}
